package util

//go:generate genny -in=syncmap_template.go -out=gen_syncmap.go gen "SyncMapKey=int SyncMapValue=*ModelRoomPubSub,*ModelPlayerStatePubSub,*ModelTimeDurationPubSub,*sudoku.AnswerCheckService"

import (
	"sync"

	"github.com/cheekybits/genny/generic"
)

// SyncMapKey is key for map
type SyncMapKey generic.Type

// SyncMapValue is value for map
type SyncMapValue generic.Type

// SyncSyncMapKeyMapSyncMapValue is a cocurrent map wraps with sync.Map
type SyncSyncMapKeyMapSyncMapValue struct {
	base       sync.Map
	baseWaitFn sync.Map
	// baseWaitFn is use to implement LoadOrStoreWait
}

// MakeSyncSyncMapKeyMapSyncMapValue make map
func MakeSyncSyncMapKeyMapSyncMapValue() SyncSyncMapKeyMapSyncMapValue {
	return SyncSyncMapKeyMapSyncMapValue{}
}

// Store is like m[key] = val
func (m *SyncSyncMapKeyMapSyncMapValue) Store(key SyncMapKey, val SyncMapValue) {
	m.base.Store(key, val)
}

// Load like m[key], return one
func (m *SyncSyncMapKeyMapSyncMapValue) Load(key SyncMapKey) SyncMapValue {
	val, _ := m.base.Load(key)
	return val.(SyncMapValue)
}

// LoadOk like m[key], return two
func (m *SyncSyncMapKeyMapSyncMapValue) LoadOk(key SyncMapKey) (SyncMapValue, bool) {
	val, ok := m.base.Load(key)
	return val.(SyncMapValue), ok
}

// Exist whether the key exist
func (m *SyncSyncMapKeyMapSyncMapValue) Exist(key SyncMapKey) bool {
	_, ok := m.base.Load(key)
	return ok
}

// Delete deletes key
func (m *SyncSyncMapKeyMapSyncMapValue) Delete(key SyncMapKey) {
	m.base.Delete(key)
	m.baseWaitFn.Delete(key)
}

// StoreOrLoad set value if not exist, return the value it get
func (m *SyncSyncMapKeyMapSyncMapValue) StoreOrLoad(key SyncMapKey, val SyncMapValue) (SyncMapValue, bool) {
	newVal, loaded := m.base.LoadOrStore(key, val)
	return newVal.(SyncMapValue), loaded
}

// StoreOrLoadWait set value if not exist, the load process will wait unitl set finished
// the callback function will produce the value
func (m *SyncSyncMapKeyMapSyncMapValue) StoreOrLoadWait(key SyncMapKey, valProducer func() (SyncMapValue, error)) (SyncMapValue, bool) {
	type loadWaitFn func() SyncMapValue

	// if exist, just return
	vali, ok := m.base.Load(key)
	if ok {
		return vali.(SyncMapValue), true
	}

	var (
		wg     sync.WaitGroup
		newVal SyncMapValue
		err    error
	)

	wg.Add(1)
	// make use of wait fn
	valfi, loaded := m.baseWaitFn.LoadOrStore(key, loadWaitFn(func() SyncMapValue {
		wg.Wait()
		return newVal
	}))

	if loaded {
		return valfi.(loadWaitFn)(), loaded
	}
	// not exist, loaded one
	newVal, err = valProducer()
	wg.Done()
	// encounter error, not save
	if err != nil {
		return newVal, true
	}
	// store into orignal map
	m.base.Store(key, newVal)

	return newVal, loaded
}
